import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'binary_codec.dart';
import 'file_data_saver.dart';

mixin DataStorer {
  static final bool defaultAutoSave =
      Platform.isAndroid || Platform.isIOS || Platform.isFuchsia;

  bool autoSave = DataStorer.defaultAutoSave;

  Future<void> save();
  Future<void> clear();

  E? get<E>(Object key, {E? defaultValue});

  Future<void> put<E>(Object key, E value);

  Future<void> delete(Object key);
}

mixin MapDataStorerObject<T> {
  Future<void> putToMapDataStorer(Object key, MapDataStorer mapDataStorer);
  T $fromMapDataStorer(Object key, MapDataStorer mapDataStorer);
}

mixin MappableObject<T> {
  T $fromMap(Map map);
  Map toMap();
}

mixin ListableObject<T> {
  T $fromList(List list);
  List toList();
}

abstract class MapDataStorer with DataStorer {
  late final Map datas;
  String get debugLabel;

  @override
  E? get<E>(Object key, {E? defaultValue}) {
    var value = datas[key];
    value = (value is E) ? value : null;
    value ??= defaultValue;
    print('$debugLabel:get:$key=$value');
    return value;
  }

  @override
  Future<void> put<E>(Object key, E? value) async {
    if (value is MapDataStorerObject) {
      return value.putToMapDataStorer(key, this);
    }
    datas[key] = value;
    print('$debugLabel:put:$key=$value');
    if (autoSave) await save();
  }

  @override
  Future<void> delete(Object key) async {
    datas.remove(key);
    print('$debugLabel:delete:$key');
    if (autoSave) await save();
  }
}

abstract class FileMapDataStorer extends MapDataStorer {
  static final separator = Platform.pathSeparator;
  final File file;
  late final String fileName;

  FileMapDataStorer(this.file,
      {bool? autoSave, bool initCreate = false, MapDataStorer? oldDataStorer}) {
    var path = file.path;
    fileName = path.substring(path.lastIndexOf(separator) + 1);
    if (autoSave != null) this.autoSave = autoSave;
    if (initCreate) {
      datas = {};
      print('initCreate $debugLabel');
      return;
    }
    if (oldDataStorer != null) {
      datas = oldDataStorer.datas;
      print('${oldDataStorer.debugLabel} migrate to $debugLabel');
      return;
    }
    initFromFile();
  }

  FileMapDataStorer.migration(this.file, MapDataStorer mapDataStorer) {
    var path = file.path;
    fileName = path.substring(path.lastIndexOf(separator) + 1);
    datas = mapDataStorer.datas;
    print('${mapDataStorer.debugLabel} migrate to $debugLabel');
  }

  void initFromFile();

  @override
  Future<void> clear() => file.delete();

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is FileMapDataStorer &&
            runtimeType == other.runtimeType &&
            file == other.file;
  }

  @override
  int get hashCode => file.hashCode;
}

class JsonDataStorer extends FileMapDataStorer with FileStringSaver {
  static JsonDataStorer openDataStorer(String filePath) =>
      JsonDataStorer(File(filePath));

  JsonDataStorer(super.file,
      {super.autoSave, super.initCreate, super.oldDataStorer});

  final JsonEncoder _encoder = const JsonEncoder();

  @override
  void initFromFile() {
    Map? map;
    if (file.existsSync()) {
      JsonDecoder decoder = const JsonDecoder();
      try {
        map = decoder.convert(file.readAsStringSync());
        print('Read JsonData: ${file.path}');
      } catch (e) {
        map = {};
        print(e);
      }
    }
    datas = map ?? {};
  }

  @override
  String getSaveString() => _encoder.convert(datas);

  @override
  File get saveStringFile => file;

  @override
  String get debugLabel => 'JsonDataStorer:$fileName';

  @override
  Future<void> save() => saveStringToFile();

  @override
  E? get<E>(Object key, {E? defaultValue}) =>
      super.get(key.toString(), defaultValue: defaultValue);

  @override
  Future<void> put<E>(Object key, E? value) => super.put(key.toString(), value);

  @override
  Future<void> delete(Object key) => super.delete(key.toString());
}

class BinaryDataStorer extends FileMapDataStorer with FileBytesSaver {
  static BinaryDataStorer openDataStorer(String filePath) =>
      BinaryDataStorer(File(filePath));

  BinaryDataStorer(super.file,
      {super.autoSave, super.initCreate, super.oldDataStorer});

  @override
  void initFromFile() {
    Map? map;
    if (file.existsSync()) {
      final decoder = Decoder();
      try {
        final reader = ByteReader(file.readAsBytesSync());
        reader.read();
        map = decoder.decodeMap(reader);
        print('Read BinaryData: ${file.path}');
      } catch (e) {
        map = {};
        print(e);
      }
    }
    datas = map ?? {};
  }

  final Encoder _encoder = Encoder();

  @override
  List<int> getSaveBytes() => _encoder.convert(datas);

  @override
  File get saveBytesFile => file;

  @override
  String get debugLabel => 'BinaryDataStorer:$fileName';

  @override
  Future<void> save() => saveBytesToFile();
}
